var build = (function(){
    var scripts = document.getElementsByTagName("script");
    for( var i=0; i<scripts.length; ++i )
    {
        var m = scripts[i].src.match( /oboe.js(?:\?([0-9]+))/ );
        if( !m ) continue;
        return m[1];
    }
})();

function bind( c, param )
{
    return function(){return c.apply(this, param);};
}

function shiftBind( c, param )
{
    return function(){return c.apply(this, param.concat(arguments));};
}

function setBind( c, param )
{
    return function(){var args = Array.prototype.slice.call(arguments, 0);for(var k in param) args[k] = param[k]; return c.apply( this, args );}
}

function pushBind( c, param )
{
    return function(){
        var args = Array.prototype.concat.apply(arguments, param);
        return c.apply(null, args);
    };
}

function thisBind( obj, func )
{
    return function(){return func.apply( obj, arguments );};
}

function thisShiftBind( obj, func, param )
{
    return function(){
        var args = Array.prototype.concat.apply(arguments, param);
        return func.apply( obj, args );
    };
}

var trace = function()
{
    if( oboe.i_ && oboe.i_.traceTarget ) oboe.i_.traceTarget.innerHTML += "<p>" + Array.prototype.join.apply(arguments, [" "] ) + "</p>";
    if( window.console && window.console.log && window.console.log.apply ) window.console.log.apply( window.console, arguments );
};

var getClass = function( name )
{
    return oboe.loadedClasses[name].factory;
};

var oboe = 
{
    scriptStateChange : function( onload )
    {
        if( this.readyState == "loaded" || this.readyState == "complete" && onload.length )
        {
            onload[0]();
            onload.pop();
        }
    },

    setupPrototype : function()
    {
        if( !Array.prototype.forEach )
        {
            Array.prototype.forEach = function(callback, thisObject){
                for( var i=0; i<this.length; ++i )
                {
                    callback.apply( this, [ this[i] ] );
                }
            };
        }
        if( !String.prototype.trim )
        {
            String.prototype.trim = function(){
                return this.replace(/^\s*|\s*$/g, "");
            }
        }
        if( !Object.create )
        {
            Object.create = function( o )
            {
                function F() {}  
                F.prototype = o;  
                return new F();
            };
        }
        if( !document.head ) document.head = document.getElementsByTagName("head")[0];
    },

    getElementsByRegex : function( prop, exp )
    {
        var all = document.getElementsByTagName("*");
        var ret = [];
        for( var i=0; i<all.length; ++i )
        {
            var e = all[i];
            if( exp.test( e[prop] ) )
                ret.push( e );
        }
        return ret;
    },

    createXHR : function()
    {
        if( window.XMLHttpRequest )
        {
            return new XMLHttpRequest();
        }
        else if(window.ActiveXObject)
        {
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
        else
        {
            throw "Your browser is not IE 5 or higher, or Firefox or Safari or Opera";
        }
    },

    getTextFile : function( url, cb )
    {
        var http = oboe.createXHR();
        http.open("GET",url);
        http.onreadystatechange = function ()
        {
            if(http.readyState == 4 && http.status == 200)
            {
                cb( http.responseText );
            }
        };
        http.send(null);
    },

    loadJS : function( src, cb )
    {
        var s = document.createElement("script");
        s.src = src + '?bid=' + build;
        s.onload = cb;
        s.onreadystatechange = bind( oboe.scriptStateChange, [[cb]] );
        document.head.appendChild( s )
    },
    
    exec : function( code ){
        var script = document.createElement("script");
        try
        {
            script.innerHTML = code;
        }
        catch(e)
        {
            script.text = code;
        }
        document.head.appendChild( script );
    },
    
    checklist : function( params, container, action, done )
    {
        var obj = {
            k : null,
            v : null,
            cb : function(){
                c--;
                if( !c ) done();
            }
        };
        var args = params.slice(0);
        var l = args.length;
        var c = 0;
        for( var k in container )
        {
            c++;
            obj.k = k;
            obj.v = container[k];
            for( var i=0; i<l; ++i ) args[i] = obj[params[i]];
            action.apply( null, args );
        }
        if( !c ) done();
    },

    
    init : function( mini )
    {
      oboe.i_.bootCode = "";
      oboe.i_.traceTarget = document.getElementById("trace");
      oboe.setupPrototype();

      var elements = oboe.getElementsByRegex("id", /^js:.+$/i);

      if( mini )
      {
        oboe.loadJS( mini, function(){
            var boot = "";
            var c = 1;
            for( var path in oboe.classData )
            {
                data = oboe.loadedClasses[path] = oboe.classData[path];
                for( var req in data.reqs )
                {
                    c++;
                    oboe.loadJS( req, function(){
                        c--;
                        if( !c ) oboe.exec(boot);
                    });
                }
                boot = "oboe.bootClass( " + path + ", '" + path + "', " + (data.base!=""?data.base:null) + ");\n" + boot;
            }
            c--;
            for( var t in elements )
            {
                src = elements[t].id.replace(/^js:(.+)$/i, "$1.js");
                var className = /([A-Z][a-zA-Z0-9_]*)\.js$/.exec( src )[1];
                boot += "\n\nvar " + className.toLowerCase() + " = new " + className + "( document.getElementById(\"" + elements[t].id + "\") );\n";
            }
            if( !c ) oboe.exec(boot);
        });
      }
      else
      {
          oboe.i_.setupTags(elements);
      }
    },
    
    classData : null,
    source : "",
    loadedClasses : {},
    
    bootClass : function( THIS, path, SUPER_THIS )
    {
        oboe.loadedClasses[path].factory = THIS;
        if( !THIS.name ) THIS.name = path;
        if( SUPER_THIS )
        {
            var base = SUPER_THIS.name;
            THIS.prototype = Object.create( SUPER_THIS.prototype );
            THIS.prototype.constructor = THIS;
            THIS.prototype.Super = function()
            {
                if( base in oboe.loadedClasses && oboe.loadedClasses[base].base == "" )
                {
                    if( this.hasOwnProperty('Super') ) delete this['Super'];
                }
                else
                {
                    this.Super = SUPER_THIS.prototype.Super;
                }
                SUPER_THIS.apply( this, arguments );
                this[base] = {};
                for( var k in this )
                {
                    if( typeof( this[k] ) != 'function' ) continue;
                    this[base][k] = thisBind( this, this[k] );
                }
            }
        }
    },   
    
    i_ : 
    {
        setupTags : function( elements ){
            oboe.checklist( ["v", "cb"], elements, oboe.i_.setupTag, function(){
                if( oboe.i_.bootCode != "" )
                {
                    oboe.exec(oboe.i_.bootCode)
                    oboe.i_.bootCode = "";
                }
                var miniTag = document.getElementById("minimize");
                if( miniTag )
                {
                    miniTag.innerHTML = "oboe.classData = " + JSON.stringify(oboe.loadedClasses) + "; " + oboe.source.replace(/\/\/.*$/gm, "");
                }
            });
        },
        setupTag : function( tag, cb )
        {
            var src;
            if( typeof tag != "string" )
            {
                src = tag.id.replace(/^js:(.+)$/i, "$1.js");
            }
            else
            {
                src = tag;
                tag = null;
            }

            var matches = /([A-Z][a-zA-Z0-9_]*)\.js$/.exec( src );
            if( !matches ) throw "Illegal class name: " + src;
            var className = matches[1];

            cb = cb || function(){};

            if( oboe.i_.loadingQueue[ className ] )
            {
                oboe.i_.loadingQueue[ className ].push( cb );
                return;
            }

            if( oboe.loadedClasses[ className ] )
            {
                cb();
                return;
            }

            oboe.i_.loadingQueue[ className ] = [ cb ];

            oboe.loadJS( src, function(){
                if( oboe.classData && className in oboe.classData )
                {
                    oboe.i_.compileClass( src, className, null, tag );
                }
                else
                {
                    oboe.getTextFile( src + '?bid=' + build, function(data){
                        oboe.i_.compileClass( src, className, data, tag );
                    });
                }
            });
        },

        loadingQueue : {},
        compileClass : function( fullPath, path, src, tag )
        {
            var deps = {};
            var reqs = {};
            var data;
            
            var cleanedSrc = src;
            // remove one-line comments
            cleanedSrc = cleanedSrc.replace(/\/\/[^%].*$/gm, "");

            // remove multi-line comments
            cleanedSrc = cleanedSrc.replace(/\/\*[^%](?:[^\*]*\*[^\/])*[^\*]*\*\//g, "");

            //remove strings
            cleanedSrc = cleanedSrc.replace(/"(?:[^\\]*\\.)*"/g, "");
            cleanedSrc = cleanedSrc.replace(/'(?:[^\\]*\\.)*'/g, "");

            var rule = /(?:this\s*\.([^\(\s]*)\s*=\s*function\s*\(([^\)]*)\))|(?:\/\*%\s*([!\w\s\.]*)\*\/)|(?:\/\/%\s*([!\w \t\.\-\+\$\?\/]*))/g;
            var find;
            var Mods = {};
            var tempMods = {};
            var base = "";
            var i;
            var rMod;

            function setupRMod(){
                rMod = {};
                for( i in Mods ) rMod[i] = Mods[i];
                for( i in tempMods ) rMod[i] = tempMods[i];
                for( i in rMod )
                {
                    if( !rMod[i] ) delete rMod[i];
                }
            }

            setupRMod();
            data = {};

            while( (find=rule.exec(cleanedSrc)) != null )
            {
                var f;
                if( find[1] && find[1] != path )
                {
                    for( f in rMod )
                    {
                        data[ find[1] ] = rMod;
                        tempMods = {};
                        setupRMod();
                        break;
                    }
                }
                else if( find[3] )
                {
                    f = find[3].trim().split(/\s+/);
                    for( i=0; i<f.length; ++i )
                    {
                        if( /^!/.test( f[i] ) )
                        {
                            tempMods[ f[i].substr(1) ] = 0;
                        }
                        else
                        {
                            tempMods[ f[i] ] = 1;
                        }
                    }
                    setupRMod();
                }
                else if( find[4] )
                {
                    var tmp;
                    f = find[4].trim().split(/\s+/);
                    for( i=0; i<f.length; ++i )
                    {
                        switch( f[i] )
                        {
                        case "extends":
                            base = f[++i];
                            deps[base] = 0;
                            base = base.match(/[A-Z][a-zA-Z0-9_]*$/)[0];
                            break;
                        case "import":
                            tmp=f[++i];
                            deps[tmp] = 0;
                            break;
                        case "require":
                            tmp=f[++i];
                            reqs[tmp] = 0;
                            break;
                        default:
                            if( /^!/.test( f[i] ) )
                            {
                                Mods[ f[i].substr(1) ] = 0;
                            }
                            else
                            {
                                Mods[ f[i] ] = 1;
                            }
                        }
                    }
                    setupRMod();
                }
            }

            data = {
                tags : data,
                base : base,
                reqs : reqs,
                deps : deps
            };

            oboe.loadedClasses[path] = data;
            oboe.source = "<p>" + src + "</p>" + oboe.source;
            var root = fullPath.replace(/([A-Z][a-zA-Z0-9_]*)\.js$/, "");

            function end(){
                var boot = "oboe.bootClass( " + path + ", '" + path + "', " + (base!=""?base:null) + ");\n";
                if( tag )
                {
                    boot += "\n\nvar " + path.toLowerCase() + " = new " + path + "( document.getElementById(\"" + tag.id + "\") );\n";
                }
                
                oboe.i_.bootCode += boot;
                
                var cbs = oboe.i_.loadingQueue[path];
                delete oboe.i_.loadingQueue[path];
                for( i=0; i<cbs.length; ++i ) cbs[i]();
            }
            
            
            oboe.checklist(
                ["k", "cb"],
                deps, 
                function(k,cb){ 
                    oboe.i_.setupTag( root+k.replace(/\./g, "/")+".js", cb );
                }, 
                function(){
                    oboe.checklist( ["k", "cb"], reqs, oboe.loadJS, end );
                }
            );
        }
    }
}
